package com.ken.astroboy.service;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Paths;

import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.net.ssl.SSLContext;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.apache.http.util.TextUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.ken.astroboy.core.Constant;
import com.ken.astroboy.model.BaseRequest;
import com.ken.astroboy.model.Contact;
import com.ken.astroboy.model.Passport;
import com.ken.astroboy.model.SyncKey;
import com.ken.astroboy.model.SyncKeyDetail;
import com.ken.astroboy.model.WebWxBatchGetContactResponse;
import com.ken.astroboy.model.WebWxGetContactResponse;
import com.ken.astroboy.model.WebWxInitResponse;
import com.ken.astroboy.model.WebWxSyncResponse;
import com.ken.astroboy.util.CreateMD5;
import com.ken.astroboy.util.EmojiConverter;
import com.ken.astroboy.util.Exceptions;
import com.ken.astroboy.util.HttpUploadFile;
import com.ken.astroboy.util.Matchers;

public class WxService{
	
	private final static Logger logger = LoggerFactory.getLogger(WxService.class);
	private static CloseableHttpClient httpClient;
	private static Gson gson;

	private static String baseUrl;
	private static String redirectUrl;
	private static String webpushUrl;		
	private static String uuid;
	private static Passport passport;		
	private static SyncKey syncKey;
	private static String syncKeyString;
	public static Contact user;
	
	private static String qrCodeFileRealPath;
	private static String avatarUrl;
	
	static{
		
		passport = new Passport();
		
		gson = new Gson();
		gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();
		
		System.setProperty("jsse.enableSNIExtension", "false");
		System.setProperty("https.protocols", "TLSv1");
		

		try {	      			
			SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(SSLContext.getDefault(),
					new String[] { "TLSv1" }, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());						
			
			RequestConfig config = RequestConfig.custom().setConnectTimeout(90000).setSocketTimeout(90000).build();
									
			httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).setDefaultRequestConfig(config).build();

		} catch (NoSuchAlgorithmException e) {
			logger.info(Exceptions.getExceptionDetail(e));
		}	
	}

	public static void getUuid() {
		
		String url = Constant.JS_LOGIN_URL;
		CloseableHttpResponse response = null;

		logger.info("获取uuid开始...");

		try{
			List<NameValuePair> list = new ArrayList<NameValuePair>();
			list.add(new BasicNameValuePair("appid", "wx782c26e4c19acffb"));
			list.add(new BasicNameValuePair("fun", "new"));
			list.add(new BasicNameValuePair("lang", "zh_CN"));
			list.add(new BasicNameValuePair("_", Long.toString(System.currentTimeMillis())));
	
			URIBuilder urlBuilder = new URIBuilder();
			urlBuilder.setPath(url);
			urlBuilder.setParameters(list);
			URI uri = urlBuilder.build();
	
			HttpGet httpGet = new HttpGet(uri);
	
			logger.debug(httpGet.getRequestLine().toString());
	
			response = httpClient.execute(httpGet);
	
			int statusCode = response.getStatusLine().getStatusCode();
				
			if (statusCode == HttpStatus.SC_OK) {
	
				HttpEntity entity = response.getEntity();
				String responseContent = EntityUtils.toString(entity, Constant.encoding);
				logger.debug(responseContent);
	
				if (!TextUtils.isBlank(responseContent)) {
					String code = findStr("window.QRLogin.code = (\\d+);", responseContent);
					if (!TextUtils.isEmpty(code) && code.equals("200")) {
						
						uuid = findStr("window.QRLogin.uuid = \"(.*)\";", responseContent);				
					}
				}
			}			
		} catch (Exception e) {
			logger.info(Exceptions.getExceptionDetail(e));
		} finally {
			try {
                if(response != null){
                    response.close();
                }
			} catch (IOException e) {
				logger.info(Exceptions.getExceptionDetail(e));
			}
		}
		
		logger.info("获取uuid结束，uuid:[{}]", uuid);
	}

	public static File getQrCode(String fileRealPath) {
		
	    qrCodeFileRealPath = fileRealPath;
	    
		String url = Constant.QRCODE_URL + uuid;
		File qrCodeFile = new File(qrCodeFileRealPath);
		CloseableHttpResponse response = null;
		
        FileOutputStream fos = null;

		logger.info("获取二维码图片开始...");		

		try {
			HttpGet httpGet = new HttpGet(url);
			
			logger.info(httpGet.getRequestLine().toString());
			
			response = httpClient.execute(httpGet);
			
			int statusCode = response.getStatusLine().getStatusCode();
									
			if (statusCode == HttpStatus.SC_OK) {
				
				HttpEntity entity = response.getEntity();
				InputStream is = entity.getContent();
				fos = new FileOutputStream(qrCodeFile);
				int l = -1;
				byte[] b = new byte[1024];
				while ((l = is.read(b)) != -1) {
					fos.write(b, 0, l);
				}
			}
		} catch (Exception e) {
			logger.info(Exceptions.getExceptionDetail(e));
		} finally {
			try {
                if(response != null){
                    response.close();
                }
				
				if(fos != null ){
				    logger.info("close fos");
                    fos.close();			    
				}

			} catch (IOException e) {
				logger.info(Exceptions.getExceptionDetail(e));
			}
		}
		
		logger.info("获取二维码图片结束");
		return qrCodeFile;
	}

	   public static File getQrCode() {
	        
	        String url = Constant.QRCODE_URL + uuid;
	        File qrCodeFile = new File(Constant.QrCodeFile);
	        CloseableHttpResponse response = null;
	        
	        logger.info("获取二维码图片开始...");        

	        try {
	            HttpGet httpGet = new HttpGet(url);
	            
	            logger.info(httpGet.getRequestLine().toString());
	            
	            response = httpClient.execute(httpGet);
	            
	            int statusCode = response.getStatusLine().getStatusCode();
	                        
	            if (statusCode == HttpStatus.SC_OK) {
	                
	                HttpEntity entity = response.getEntity();
	                InputStream is = entity.getContent();
	                FileOutputStream fos = new FileOutputStream(qrCodeFile);
	                int l = -1;
	                byte[] b = new byte[1024];
	                while ((l = is.read(b)) != -1) {
	                    fos.write(b, 0, l);
	                }
	                fos.close();
	            }
	        } catch (Exception e) {
	            logger.info(Exceptions.getExceptionDetail(e));
	        } finally {
	            try {
	                if(response != null){
	                    response.close();
	                }	                
	            } catch (IOException e) {
	                logger.info(Exceptions.getExceptionDetail(e));
	            }
	        }
	        
	        logger.info("获取二维码图片结束");
	        return qrCodeFile;
	    }

	private static String getLoginStatus(String tip, long currentTimeMilli) {
		
		String url = Constant.LOGIN_URL;
		String responseContent = null;
		CloseableHttpResponse response = null;
		
		logger.info("获取登录状态开始...");

		try {
			List<NameValuePair> list = new ArrayList<NameValuePair>();
			list.add(new BasicNameValuePair("loginicon", "true"));
			list.add(new BasicNameValuePair("uuid", uuid));
			list.add(new BasicNameValuePair("tip", tip));
			list.add(new BasicNameValuePair("r", getR()));
			list.add(new BasicNameValuePair("_", Long.toString(currentTimeMilli)));

			URIBuilder urlBuilder = new URIBuilder();
			urlBuilder.setPath(url);
			urlBuilder.setParameters(list);
			URI uri = urlBuilder.build();

			HttpGet httpGet = new HttpGet(uri);
			
			logger.debug(httpGet.getRequestLine().toString());
			
			response = httpClient.execute(httpGet);
			
			
			int statusCode = response.getStatusLine().getStatusCode();
			
			if (statusCode == HttpStatus.SC_OK) {
				HttpEntity entity = response.getEntity();
				responseContent = EntityUtils.toString(entity, Constant.encoding);
				
	            logger.debug(responseContent);

			}
		} catch (Exception e) {
			logger.info(Exceptions.getExceptionDetail(e));
		} finally {
			try {
                if(response != null){
                    response.close();
                }
			} catch (IOException e) {
				logger.info(Exceptions.getExceptionDetail(e));
			}
		}
		
		logger.info("获取登录状态结束");
		return responseContent;
	}


	public static boolean isLoginOK(){
		
		String tip = "1";// 首次时为1
		long currentTimeMillis = System.currentTimeMillis();// 首次时为系统时间，之后每次请求加1
				
		while(true){
			
			String responseContent = getLoginStatus(tip, currentTimeMillis++);
			
			tip = "0";
			
			if ( responseContent == null ) {			
				continue;
			}else{
				String code = findStr("window.code=(\\d+);", responseContent);
				
				if (null == code) {				
					continue;
				} else {
					if (code.equals("201")) {
						logger.info("成功扫描,请在手机上点击确认登录");
						try {
							Thread.sleep(2000);
						} catch (InterruptedException e) {
							logger.info(Exceptions.getExceptionDetail(e));
						}//休眠2s后再检查是否点击确认
												
					} else if (code.equals("200")) {
						logger.info("手机已经点击确认登录");
						
						redirectUrl = findStr("window.redirect_uri=\"(\\S+?)\";", responseContent) + "&fun=new";
						baseUrl = redirectUrl.substring(0, redirectUrl.lastIndexOf("/"));				
						
						logger.info("redirect_uri: [{}]", redirectUrl);
						logger.info("base_uri: [{}]", baseUrl);
						
						break;
					} else if (code.equals("408")) {
						logger.info("登录超时，请关闭程序重新登录");				
						return false;
						
					} else {
						logger.info("未知的登录状态code：{}", code + "，继续尝试检查登录状态");					
						continue;
					}
				}			
				
			}
		}			
		return true;
	}	

	public static String checkLoginStatus(String tip, long currentTimeMillis){

	    String code = null;

	    String responseContent = getLoginStatus(tip, currentTimeMillis++);

	    logger.info("responseContent of getLoginStatus:");
	    logger.info(responseContent);
	    
	    if ( responseContent != null ) {            

	        code = findStr("window.code=(\\d+);", responseContent);

	        if (code != null) {             
	            if (code.equals("201")) {
	                
	                avatarUrl = findStr("window.userAvatar = '(\\S+?)';", responseContent);
	                logger.info("avatarUrl: [{}]", avatarUrl);
	                logger.info("成功扫描,请在手机上点击确认登录");

	            } else if (code.equals("200")) {
	                logger.info("手机已经点击确认登录");

	                redirectUrl = findStr("window.redirect_uri=\"(\\S+?)\";", responseContent) + "&fun=new";
	                baseUrl = redirectUrl.substring(0, redirectUrl.lastIndexOf("/"));               

	                logger.info("redirect_uri: [{}]", redirectUrl);
	                logger.info("base_uri: [{}]", baseUrl);

	            } else if (code.equals("408")) {
	                logger.info("登录超时，请关闭程序重新登录");              

	            } else {
	                logger.info("未知的登录状态code：{}", code + "，继续尝试检查登录状态");                    
	            }
	        }
	    }

	    return code;
	}   
	
	public static void getPassport() {
		
		CloseableHttpResponse response = null;
		
		logger.info("获取Cookie, BaseRequest(Uin, Sid, Skey), pass_ticket开始...");			

		try {
			
			HttpGet httpGet = new HttpGet(redirectUrl);
			
			logger.debug(httpGet.getRequestLine().toString());
			
			response = httpClient.execute(httpGet);
			
			logger.debug(response.toString());
			
			int statusCode = response.getStatusLine().getStatusCode();
			
			if (statusCode == HttpStatus.SC_OK) {
				HttpEntity entity = response.getEntity();
				String responseContent = EntityUtils.toString(entity, Constant.encoding);
				
				logger.debug(responseContent);

				passport.getBaseRequest().setSkey(findStr("<skey>(\\S+)</skey>", responseContent));
				passport.getBaseRequest().setSid(findStr("<wxsid>(\\S+)</wxsid>", responseContent));
				passport.getBaseRequest().setUin(findStr("<wxuin>(\\S+)</wxuin>", responseContent));
				
				passport.setPassTicket(findStr("<pass_ticket>(\\S+)</pass_ticket>", responseContent));

				Header[] headers = response.getAllHeaders();
				StringBuilder sb = new StringBuilder();
				
				for(Header header : headers){
					logger.info(header.getName() + " -> " + header.getValue());
					if(header.getName().equals("Set-Cookie")){
						String value = header.getValue();
						if(value != null){
							sb.append(value.substring(0, value.indexOf(";") + 1));							
						}
					}
				}
				
			    passport.setCookie(sb.toString());
			    
			}
		} catch (Exception e) {
			logger.info(Exceptions.getExceptionDetail(e));
		} finally {
			try {
                if(response != null){
                    response.close();
                }
			} catch (IOException e) {
				logger.info(Exceptions.getExceptionDetail(e));
			}
		}

		logger.info("获取Cookie, BaseRequest(Uin, Sid, Skey, DeviceID), pass_ticket结束");
		logger.info("--BaseRequest-Uin:[{}], Sid:[{}], Skey:[{}]", passport.getBaseRequest().getUin(),
				                                                 passport.getBaseRequest().getSid(),
				                                                 passport.getBaseRequest().getSkey());
		logger.info("--pass_ticket:[{}]", passport.getPassTicket());
		logger.info("--Cookie:[{}]", passport.getCookie());

		logger.info("获取Cookie, BaseRequest(Uin, Sid, Skey), pass_ticket完成");
	}

	
	public static WebWxInitResponse webWxInit() {
		
		String url = baseUrl + "/webwxinit";
		WebWxInitResponse webWxInitResponse = null;
		CloseableHttpResponse response = null;
		
		logger.info("微信初始化开始...");

		try {
			List<NameValuePair> list = new ArrayList<NameValuePair>();
			list.add(new BasicNameValuePair("r", getR()));
			list.add(new BasicNameValuePair("lang", "zh_CN"));
			list.add(new BasicNameValuePair("pass_ticket", passport.getPassTicket()));

			URIBuilder urlBuilder = new URIBuilder();
			urlBuilder.setPath(url);
			urlBuilder.setParameters(list);
			URI uri = urlBuilder.build();

			BaseRequest br = passport.getBaseRequest();
			// 生成15位随机数;
			br.setDeviceId("e" + System.currentTimeMillis() + passport.getBaseRequest().getUin().substring(0, 2));
			
			String json = "{\"BaseRequest\":{\"Uin\":\"" + br.getUin() + "\",\"Sid\":\"" + br.getSid() + "\"," + "\"Skey\":\""
					+ br.getSkey() + "\",\"DeviceID\":\"" + br.getDeviceId() + "\"}}";
											
			logger.debug(json);
			
			StringEntity stringEntity = new StringEntity(json, ContentType.APPLICATION_JSON);

			HttpPost httpPost = new HttpPost(uri);			
			httpPost.setEntity(stringEntity);
			httpPost.setHeader("Cookie", passport.getCookie());
			
			logger.debug(httpPost.getRequestLine().toString());
			
			response = httpClient.execute(httpPost);
			
			logger.debug(response.toString());
			
			int statusCode = response.getStatusLine().getStatusCode();
			
			if (statusCode == HttpStatus.SC_OK) {
				
				HttpEntity entity = response.getEntity();
				String responseContent = EntityUtils.toString(entity, Constant.encoding);
				
				logger.debug(responseContent);

				webWxInitResponse = gson.fromJson(responseContent, WebWxInitResponse.class);
				
				user = webWxInitResponse.getUser();
				syncKey = webWxInitResponse.getSyncKey();
				
				StringBuffer synckey = new StringBuffer();
				
				for(SyncKeyDetail detail : syncKey.getList()){
					synckey.append("|" + detail.getKey() + "_" + detail.getVal());
				}

				syncKeyString = synckey.substring(1);	
			}
		} catch (Exception e) {
			logger.info(Exceptions.getExceptionDetail(e));
		} finally {
			try {
				if(response != null){
					response.close();
				}
			} catch (IOException e) {
				logger.info(Exceptions.getExceptionDetail(e));
			}
		}
		
		logger.info("微信初始化结束");
		return webWxInitResponse;
	}
	
	
	public static void webWxStatusNotify() {

		String url = baseUrl + "/webwxstatusnotify";
		CloseableHttpResponse response = null;
		
		logger.info("开启微信状态通知开始...");

		try {
			
			BaseRequest br = passport.getBaseRequest();
			// 生成15位随机数;
			br.setDeviceId("e" + System.currentTimeMillis() + passport.getBaseRequest().getUin().substring(0, 2));
			
			String json = "{\"BaseRequest\":{\"Uin\":\"" + br.getUin() + "\",\"Sid\":\"" + br.getSid() + "\"," + "\"Skey\":\""
					+ br.getSkey() + "\",\"DeviceID\":\"" + br.getDeviceId() + "\"},\"Code\":3," + "\"FromUserName\":\"" 
					+ user.getUserName() + "\",\"ToUserName\":\"" + user.getUserName() 
					+ "\",\"ClientMsgId\":\"" + System.currentTimeMillis() + "\"}";						
			
			logger.debug(json);

			StringEntity stringEntity = new StringEntity(json, ContentType.APPLICATION_JSON);

			HttpPost httpPost = new HttpPost(url);
			httpPost.setEntity(stringEntity);
			httpPost.setHeader("Cookie", passport.getCookie());
			
			logger.debug(httpPost.getRequestLine().toString());
			
			response = httpClient.execute(httpPost);
			
			logger.debug(response.toString());
			
			int statusCode = response.getStatusLine().getStatusCode();
			
			if (statusCode == HttpStatus.SC_OK) {
				
				HttpEntity entity = response.getEntity();
				String responseContent = EntityUtils.toString(entity, Constant.encoding);
				
				logger.debug(responseContent);
				
				JsonParser parser = new JsonParser();
				JsonElement element = parser.parse(responseContent);

				JsonObject object = null;
				if(element.isJsonObject()){
					object = element.getAsJsonObject(); 
					
					int ret = object.getAsJsonObject("BaseResponse").get("Ret").getAsInt();
					if(ret == 0){
						logger.info("开始微信状态通知: 成功");
					}
				}
								
			}
		} catch (Exception e) {
			logger.info(Exceptions.getExceptionDetail(e));
		} finally {
			try {
                if(response != null){
                    response.close();
                }
			} catch (IOException e) {
				logger.info(Exceptions.getExceptionDetail(e));
			}
		}
		
		logger.info("开启微信状态通知结束");
	}

	
	public static WebWxGetContactResponse getContact(long seq) {

		logger.info("开始获取联系人...");
		
		logger.info("请求seq = {}", seq);

		String url = baseUrl + "/webwxgetcontact";	
		WebWxGetContactResponse webWxGetContactResponse = null;
		CloseableHttpResponse response = null;

		try{
			List<NameValuePair> list = new ArrayList<NameValuePair>();
			list.add(new BasicNameValuePair("pass_ticket", passport.getPassTicket()));
			list.add(new BasicNameValuePair("lang", "zh_CN"));
			list.add(new BasicNameValuePair("seq", String.valueOf(seq)));
			list.add(new BasicNameValuePair("skey", passport.getBaseRequest().getSkey()));
			list.add(new BasicNameValuePair("r", getR()));

			URIBuilder urlBuilder = new URIBuilder();
			urlBuilder.setPath(url);
			urlBuilder.setParameters(list);
			URI uri = urlBuilder.build();

			BaseRequest br = passport.getBaseRequest();
			// 生成15位随机数;
			br.setDeviceId("e" + System.currentTimeMillis() + passport.getBaseRequest().getUin().substring(0, 2));

			String json = "{\"BaseRequest\":{\"Uin\":\"" + br.getUin() + "\",\"Sid\":\"" + br.getSid() + "\"," + "\"Skey\":\""
					+ br.getSkey() + "\",\"DeviceID\":\"" + br.getDeviceId() + "\"}}";

			logger.debug(json);

			StringEntity stringEntity = new StringEntity(json, ContentType.APPLICATION_JSON);

			HttpPost httpPost = new HttpPost(uri);

			httpPost.setEntity(stringEntity);
			httpPost.setHeader("Cookie", passport.getCookie());

			logger.debug(httpPost.getRequestLine().toString());

			response = httpClient.execute(httpPost);

			logger.debug(response.toString());

			int statusCode = response.getStatusLine().getStatusCode();

			if (statusCode == HttpStatus.SC_OK) {

				HttpEntity entity = response.getEntity();
				String responseContent = EntityUtils.toString(entity, Constant.encoding);

				logger.debug(responseContent);

				webWxGetContactResponse = gson.fromJson(responseContent, WebWxGetContactResponse.class);
			}

		} catch (Exception e) {
			logger.info(Exceptions.getExceptionDetail(e));
		} finally {
			try {
                if(response != null){
                    response.close();
                }
			} catch (IOException e) {
				logger.info(Exceptions.getExceptionDetail(e));
			}
		}

		logger.info("返回seq = {}", webWxGetContactResponse.getSeq());
		logger.info("获取联系人完成");
		return webWxGetContactResponse;
	}

	public static WebWxBatchGetContactResponse getBatchContact(Map<String, Contact> groupMap, boolean encry) {

		logger.info("开始获取联系人（微信群或者好友）详情...");
		
		String url = baseUrl + "/webwxbatchgetcontact";	
		WebWxBatchGetContactResponse webWxBatchGetContactResponse = null;
		CloseableHttpResponse response = null;

		try{
			List<NameValuePair> list = new ArrayList<NameValuePair>();
			list.add(new BasicNameValuePair("type", "ex"));
			list.add(new BasicNameValuePair("r", getR()));
			list.add(new BasicNameValuePair("lang", "zh_CN"));
			list.add(new BasicNameValuePair("pass_ticket", passport.getPassTicket()));

			URIBuilder urlBuilder = new URIBuilder();
			urlBuilder.setPath(url);
			urlBuilder.setParameters(list);
			URI uri = urlBuilder.build();

			BaseRequest br = passport.getBaseRequest();
			// 生成15位随机数;
			br.setDeviceId("e" + System.currentTimeMillis() + passport.getBaseRequest().getUin().substring(0, 2));
	
			JsonArray groupList = new JsonArray();
			int count = 0;
	
			for(Map.Entry<String, Contact> entry: groupMap.entrySet()){
	
				if(entry.getValue() != null){
					JsonObject group = new JsonObject();
					group.addProperty("UserName", entry.getValue().getUserName());
						if(encry){
							group.addProperty("EncryChatRoomId", entry.getValue().getEncryChatRoomId());
							logger.info("微信群:[{}] UserName:[{}] EncryChatRoomId:[{}]", entry.getKey(), entry.getValue().getUserName(),
									                                              entry.getValue().getEncryChatRoomId());
						}else{
							group.addProperty("ChatRoomId", "");
							logger.info("微信群:[{}] UserName:[{}] ChatRoomId:[]", entry.getKey(), entry.getValue().getUserName());
						}

						groupList.add(group);					
						count++;
				}else{
					logger.info("微信群[{}]尚未获得UserName", entry.getKey());
				}
				
			}			
			
			String json = "{\"BaseRequest\":{\"Uin\":\"" + br.getUin() + "\",\"Sid\":\"" + br.getSid() + "\"," + "\"Skey\":\""
					       + br.getSkey() + "\",\"DeviceID\":\"" + br.getDeviceId() + "\"},"
					       + "\"Count\":\""+ count + "\"," + "\"List\":"+ groupList + "}";

			logger.debug(json);

			StringEntity stringEntity = new StringEntity(json, ContentType.APPLICATION_JSON);

			HttpPost httpPost = new HttpPost(uri);

			httpPost.setEntity(stringEntity);
			httpPost.setHeader("Cookie", passport.getCookie());

			logger.debug(httpPost.getRequestLine().toString());

			response = httpClient.execute(httpPost);

			logger.debug(response.toString());

			int statusCode = response.getStatusLine().getStatusCode();

			if (statusCode == HttpStatus.SC_OK) {

				HttpEntity entity = response.getEntity();
				String responseContent = EntityUtils.toString(entity, Constant.encoding);

				logger.debug(responseContent);

				webWxBatchGetContactResponse = gson.fromJson(responseContent, WebWxBatchGetContactResponse.class);
			}

		} catch (Exception e) {
			logger.info(Exceptions.getExceptionDetail(e));
		} finally {
			try {
                if(response != null){
                    response.close();
                }
			} catch (IOException e) {
				logger.info(Exceptions.getExceptionDetail(e));
			}
		}

		logger.info("获取联系人（微信群或者好友）详情完成");
		return webWxBatchGetContactResponse;	
		
	}
	
	public static WebWxBatchGetContactResponse getBatchContact(Collection<Contact> contactList, boolean encry) {

		logger.info("开始获取联系人（微信群或者好友）详情...");
		
		String url = baseUrl + "/webwxbatchgetcontact";	
		WebWxBatchGetContactResponse webWxBatchGetContactResponse = null;
		CloseableHttpResponse response = null;

		try{
			List<NameValuePair> list = new ArrayList<NameValuePair>();
			list.add(new BasicNameValuePair("type", "ex"));
			list.add(new BasicNameValuePair("r", getR()));
			list.add(new BasicNameValuePair("lang", "zh_CN"));
			list.add(new BasicNameValuePair("pass_ticket", passport.getPassTicket()));

			URIBuilder urlBuilder = new URIBuilder();
			urlBuilder.setPath(url);
			urlBuilder.setParameters(list);
			URI uri = urlBuilder.build();

			BaseRequest br = passport.getBaseRequest();
			// 生成15位随机数;
			br.setDeviceId("e" + System.currentTimeMillis() + passport.getBaseRequest().getUin().substring(0, 2));
	
			JsonArray groupList = new JsonArray();
			int count = 0;
	
			for(Contact contact: contactList){

				JsonObject group = new JsonObject();
				group.addProperty("UserName", contact.getUserName());
				
				if(encry){
					group.addProperty("EncryChatRoomId", contact.getEncryChatRoomId());
				}else{
					group.addProperty("ChatRoomId", "");
				}

				groupList.add(group);					
				count++;
			}			
			
			String json = "{\"BaseRequest\":{\"Uin\":\"" + br.getUin() + "\",\"Sid\":\"" + br.getSid() + "\"," + "\"Skey\":\""
					       + br.getSkey() + "\",\"DeviceID\":\"" + br.getDeviceId() + "\"},"
					       + "\"Count\":\""+ count + "\"," + "\"List\":"+ groupList + "}";

			logger.debug(json);

			StringEntity stringEntity = new StringEntity(json, ContentType.APPLICATION_JSON);

			HttpPost httpPost = new HttpPost(uri);

			httpPost.setEntity(stringEntity);
			httpPost.setHeader("Cookie", passport.getCookie());

			logger.debug(httpPost.getRequestLine().toString());

			response = httpClient.execute(httpPost);

			logger.debug(response.toString());

			int statusCode = response.getStatusLine().getStatusCode();

			if (statusCode == HttpStatus.SC_OK) {

				HttpEntity entity = response.getEntity();
				String responseContent = EntityUtils.toString(entity, Constant.encoding);

				logger.debug(responseContent);

				webWxBatchGetContactResponse = gson.fromJson(responseContent, WebWxBatchGetContactResponse.class);
			}

		} catch (Exception e) {
			logger.info(Exceptions.getExceptionDetail(e));
		} finally {
			try {
                if(response != null){
                    response.close();
                }
			} catch (IOException e) {
				logger.info(Exceptions.getExceptionDetail(e));
			}
		}

		logger.info("获取联系人（微信群或者好友）详情完成");
		return webWxBatchGetContactResponse;			
	}

	public static boolean chooseSyncLine() throws RuntimeException {
		
		for(String syncUrl : Constant.SYNC_HOST){
			
			String url = "https://" + syncUrl + "/cgi-bin/mmwebwx-bin/synccheck";

			logger.info("尝试线路：[{}]", url);
			
			int[] res = syncCheck(url);
			
			if(res[0] == 0){
				webpushUrl = url;
				logger.info("OK!选择线路：[{}]", syncUrl);
				return true;
			}
		}
		
		logger.info("出错-无可用的线路");
		return false;
	}

	public static int[] syncCheck(String... in) throws RuntimeException {

		logger.info("syncCheck开始...");

		String url = webpushUrl;

		if (in != null) {
			if (in.length != 0) {
				url = in[0];
			}
		}else {
			throw new RuntimeException("出错-输入参数为null");
		}

		int[] arr = new int[]{-1, -1};
		CloseableHttpResponse response = null;

		try {

			BaseRequest br = passport.getBaseRequest();
			// 生成15位随机数;
			br.setDeviceId("e" + System.currentTimeMillis() + passport.getBaseRequest().getUin().substring(0, 2));

			List<NameValuePair> list = new ArrayList<NameValuePair>();
			list.add(new BasicNameValuePair("r", getR()));
			list.add(new BasicNameValuePair("skey", br.getSkey()));
			list.add(new BasicNameValuePair("sid", br.getSid()));
			list.add(new BasicNameValuePair("uin", br.getUin()));
			list.add(new BasicNameValuePair("deviceid", br.getDeviceId()));
			list.add(new BasicNameValuePair("synckey", syncKeyString));
			list.add(new BasicNameValuePair("_", Long.toString(System.currentTimeMillis())));

			URIBuilder urlBuilder = new URIBuilder();
			urlBuilder.setPath(url);
			urlBuilder.setParameters(list);
			URI uri = urlBuilder.build();

			HttpGet httpGet = new HttpGet(uri);
			httpGet.setHeader("Cookie", passport.getCookie());

			logger.debug("请求参数：");
			logger.debug(httpGet.getRequestLine().toString());

			response = httpClient.execute(httpGet);
			
			logger.debug("响应：");
			logger.debug(response.toString());

			int statusCode = response.getStatusLine().getStatusCode();
			logger.debug("statusCode：" + statusCode);
			
			if (statusCode == HttpStatus.SC_OK) {

				HttpEntity entity = response.getEntity();
				String responseContent = EntityUtils.toString(entity, Constant.encoding);
				logger.debug(responseContent);

				if (!TextUtils.isBlank(responseContent)) {

					String retcodeStr = findStr("retcode:\"(\\d+)\"", responseContent);
					String selectorStr = findStr("selector:\"(\\d+)\"}", responseContent);

					if (!TextUtils.isBlank(retcodeStr) && !TextUtils.isBlank(selectorStr)) {

						arr[0] = Integer.valueOf(retcodeStr);
						arr[1] = Integer.valueOf(selectorStr);
					}
				}
			}
		} catch (Exception e) {
			logger.info(Exceptions.getExceptionDetail(e));
		} finally {
			try {
				logger.debug("finally,关闭response对象");
				if(response != null){
					response.close();
				}
			} catch (IOException e) {
				logger.info(Exceptions.getExceptionDetail(e));
			}
		}

		logger.info("syncCheck结束");
		return arr;
	}
	
	
	public static WebWxSyncResponse webWxSync() {
		
		logger.info("WebWxSync开始...");
		
		String url = baseUrl + "/webwxsync";
		WebWxSyncResponse webWxSyncResponse = null;
		CloseableHttpResponse response = null;	
		
		try {
			List<NameValuePair> list = new ArrayList<NameValuePair>();
			list.add(new BasicNameValuePair("sid", passport.getBaseRequest().getSid()));
			list.add(new BasicNameValuePair("skey", passport.getBaseRequest().getSkey()));

			URIBuilder urlBuilder = new URIBuilder();
			urlBuilder.setPath(url);
			urlBuilder.setParameters(list);
			URI uri = urlBuilder.build();
			
			JsonObject object = new JsonObject();
						
			object.add("BaseRequest", new JsonParser().parse(gson.toJson(passport.getBaseRequest())).getAsJsonObject());
			object.add("SyncKey", new JsonParser().parse(gson.toJson(syncKey)).getAsJsonObject());						
			object.addProperty("rr", getR());

			String json = gson.toJson(object);
			
			logger.debug(json);
			
			StringEntity stringEntity = new StringEntity(json, ContentType.APPLICATION_JSON);

			HttpPost httpPost = new HttpPost(uri);
			httpPost.setEntity(stringEntity);
			httpPost.setHeader("Cookie", passport.getCookie());

			logger.debug(httpPost.getRequestLine().toString());
			
			response = httpClient.execute(httpPost);
			
			logger.debug(response.toString());

			
			int statusCode = response.getStatusLine().getStatusCode();
						
			if (statusCode == HttpStatus.SC_OK) {
				
				HttpEntity entity = response.getEntity();
				String responseContent = EntityUtils.toString(entity, Constant.encoding);
				
				logger.debug(responseContent);
				
				if (!TextUtils.isBlank(responseContent)) {
					
					webWxSyncResponse = gson.fromJson(responseContent, WebWxSyncResponse.class);
					
					if ( webWxSyncResponse.getBaseResponse() != null ) {
						long ret = webWxSyncResponse.getBaseResponse().getRet();
						if (ret == 0) {
							
							//更新SyncKey
							syncKey = webWxSyncResponse.getSyncKey();
							
							StringBuffer synckey = new StringBuffer();
							
							for(SyncKeyDetail detail : syncKey.getList()){
								synckey.append("|" + detail.getKey() + "_" + detail.getVal());
							}

							syncKeyString = synckey.substring(1);								
						}
					}					
				}
			}
		} catch (Exception e) {
			logger.info(Exceptions.getExceptionDetail(e));
		} finally {
			try {
                if(response != null){
                    response.close();
                }
			} catch (IOException e) {
				logger.info(Exceptions.getExceptionDetail(e));
			}
		}
		
		return webWxSyncResponse;

	}	


	public static void sendTextMsg(String content, String toUserName) {

		logger.info("开始发送文字消息...");
		
		if(toUserName == null){			
			logger.info("toUserName参数为空，无法发送");
			return;
		}
		
		if(StringUtils.isBlank(content)){			
			logger.info("content参数为空，没内容可发送");
			return;
		}		
		
		
		String url = baseUrl + "/webwxsendmsg";
		CloseableHttpResponse response = null;	
		
		try {
			List<NameValuePair> list = new ArrayList<NameValuePair>();
			list.add(new BasicNameValuePair("lang", "zh_CN"));
			list.add(new BasicNameValuePair("pass_ticket", passport.getPassTicket()));

			URIBuilder urlBuilder = new URIBuilder();
			urlBuilder.setPath(url);
			urlBuilder.setParameters(list);
			URI uri = urlBuilder.build();
			
			logger.debug("原始消息内容：" + content);		
			String s = EmojiConverter.convert(content);
			logger.info("发送内容：" + s);

			String randomId = String.valueOf(System.currentTimeMillis());
			Random random = new Random();
			for (int i = 0; i < 4; i++) {
				randomId += random.nextInt(10);
			}
			
			JsonObject out = new JsonObject();
			JsonObject msg = new JsonObject();

			msg.addProperty("Type", 1);
			msg.addProperty("Content", s);
			msg.addProperty("FromUserName", user.getUserName());
			msg.addProperty("ToUserName", toUserName);
			msg.addProperty("LocalID", randomId);
			msg.addProperty("ClientMsgId", randomId);
			
			out.add("BaseRequest", new JsonParser().parse(gson.toJson(passport.getBaseRequest())).getAsJsonObject());
			out.add("Msg", msg);
			out.addProperty("Scene", 0);

			String json = gson.toJson(out);
			
			logger.debug(json);					
			
			StringEntity stringEntity = new StringEntity(json, ContentType.APPLICATION_JSON);

			HttpPost httpPost = new HttpPost(uri);
			httpPost.setEntity(stringEntity);
			httpPost.setHeader("Cookie", passport.getCookie());

			logger.debug(httpPost.getRequestLine().toString());
			
			response = httpClient.execute(httpPost);
			
			logger.debug(response.toString());
			
			int statusCode = response.getStatusLine().getStatusCode();
						
			if (statusCode == HttpStatus.SC_OK) {
				logger.info("文字消息发送成功");
			}
		} catch (Exception e) {
			logger.info(Exceptions.getExceptionDetail(e));
		} finally {
			try {
                if(response != null){
                    response.close();
                }
			} catch (IOException e) {
				logger.info(Exceptions.getExceptionDetail(e));
			}
		}		
	}

	public static void sendImgMsg(String fileName, String toUserName){
		
		logger.info("开始发送图片...");
		
		if(toUserName == null){			
			logger.info("toUserName参数为空，无法发送");
			return;
		}
		
		if(StringUtils.isBlank(fileName)){			
			logger.info("fileName参数为空，没内容可发送");
			return;
		}
		
		if(!new File(fileName).exists()){			
			logger.info("文件{}不存在，没内容可发送", fileName);
			return;
		}		
		
		String res = null;
		try {
			res = uploadFile(fileName, true, false);
		} catch (IOException e) {
			logger.info(Exceptions.getExceptionDetail(e));
		}
		
		if (res == null){
			logger.info("图片上传失败");
			return;
		}
		
		String url = baseUrl + "/webwxsendmsgimg";
		CloseableHttpResponse response = null;	
		
		try {
			List<NameValuePair> list = new ArrayList<NameValuePair>();
			list.add(new BasicNameValuePair("fun", "async"));
			list.add(new BasicNameValuePair("f", "json"));
			list.add(new BasicNameValuePair("pass_ticket", passport.getPassTicket()));

			URIBuilder urlBuilder = new URIBuilder();
			urlBuilder.setPath(url);
			urlBuilder.setParameters(list);
			URI uri = urlBuilder.build();

			String randomId = String.valueOf(System.currentTimeMillis());
			Random random = new Random();
			for (int i = 0; i < 4; i++) {
				randomId += random.nextInt(10);
			}
						
			JsonObject out = new JsonObject();
			JsonObject msg = new JsonObject();

			msg.addProperty("Type", 3);
			msg.addProperty("Content", "");
			msg.addProperty("FromUserName", user.getUserName());
			msg.addProperty("ToUserName", toUserName);
			msg.addProperty("LocalID", randomId);
			msg.addProperty("ClientMsgId", randomId);
			msg.addProperty("MediaId", new JsonParser().parse(res).getAsJsonObject().get("MediaId").getAsString());
			
			out.add("BaseRequest", new JsonParser().parse(gson.toJson(passport.getBaseRequest())).getAsJsonObject());
			out.add("Msg", msg);
			out.addProperty("Scene", 0);

			String json = gson.toJson(out);
			
			logger.debug(json);					
			
			StringEntity stringEntity = new StringEntity(json, ContentType.APPLICATION_JSON);

			HttpPost httpPost = new HttpPost(uri);
			httpPost.setEntity(stringEntity);
			httpPost.setHeader("Cookie", passport.getCookie());

			logger.debug(httpPost.getRequestLine().toString());
			
			response = httpClient.execute(httpPost);
			
			logger.debug(response.toString());
			
			int statusCode = response.getStatusLine().getStatusCode();
						
			if (statusCode == HttpStatus.SC_OK) {
				logger.info("图片发送成功");
			}
		} catch (Exception e) {
			logger.info(Exceptions.getExceptionDetail(e));
		} finally {
			try {
                if(response != null){
                    response.close();
                }
			} catch (IOException e) {
				logger.info(Exceptions.getExceptionDetail(e));
			}
		}			
	}

	
	//上传文件（发送图片、文档、视频等需要先上传到微信服务器）
	private static String uploadFile(String fileName, boolean isPicture, boolean isVideo) throws IOException {

		JsonObject uploadMediaRequest = new JsonObject();
		
		File file = new File(fileName);		
		long fileSzie = file.length(); 
		logger.info("上传的文件大小：" + Long.toString(fileSzie));
        byte fileContent[] = new byte[(int)fileSzie];         
        FileInputStream in = null;
        
        try
        {
        	in = new FileInputStream(file);
            in.read(fileContent);
        }catch(Exception e){
        	logger.info(Exceptions.getExceptionDetail(e));
        	return null;
        }finally{
            try {
				in.close();
			} catch (IOException e) {
				logger.info(Exceptions.getExceptionDetail(e));
				return null;
			}
        }		
        
		uploadMediaRequest.addProperty("UploadType", 2);
		uploadMediaRequest.add("BaseRequest", new JsonParser().parse(gson.toJson(passport.getBaseRequest())).getAsJsonObject());
		uploadMediaRequest.addProperty("ClientMediaId", System.currentTimeMillis());
		uploadMediaRequest.addProperty("TotalLen", fileSzie);
		uploadMediaRequest.addProperty("StartPos", 0);
		uploadMediaRequest.addProperty("DataLen", fileSzie);
		uploadMediaRequest.addProperty("MediaType", 4);
		uploadMediaRequest.add("FromUserName", new JsonParser().parse(gson.toJson(user)).getAsJsonObject());						
		uploadMediaRequest.addProperty("ToUserName", "filehelper");
		
		String fileMd5 = "";
		
		try{
			fileMd5 = CreateMD5.getMd5(new String(fileContent));
		}catch(Exception e){
			logger.info(Exceptions.getExceptionDetail(e));
		}	
		
		//通过网页抓取的f:/temp/plan.jpg md5: 36bc086914bfd34e5f8a23b158e978a2
		//本程序计算的md5                   ：                                      a0107abd2aac4d2e129b87df341df7fb
		//奇怪的是只有明月通过白色iPad发送图片成功
		logger.info("上传文件Md5：" + fileMd5);
		uploadMediaRequest.addProperty("FileMd5", fileMd5);
					
		String fileSymbol = isPicture? "pic" : (isVideo? "video":"doc");
		    
        	
        int chunks = (int) (fileSzie - 1) / 524288 + 1;
        
        String res = null;

        //实测不需要分也可以上传图片成功，这里先设为1，以后再研究。
        chunks = 1;
        
        for(int chunk = 0; chunk < chunks; chunk++){
        	
        	res = uploadTrunkFile(fileName, fileSymbol, fileSzie,
        						  chunks, chunk, uploadMediaRequest);	
        	
        	if(res == null){
        		return null;
        	}
        }
    
        return res;
    		
	}
		

	//文件部分上传（文件如果太大要分割为多个部分（chunk）上传
	private static String uploadTrunkFile(String fileName, String fileSymbol,
			                            long fileSize, int chunks, int chunk, 
			                            JsonObject uploadMediaRequest) throws IOException {

		Map<String, String> textMap = new HashMap<String, String>();

		File file = new File(fileName);
				
		textMap.put("id", "WU_FILE_" + Integer.toString(chunk));  //WU_FILE_ 后面那个数字是多少似乎没关系		
		textMap.put("name", file.getName());
		//上传文件的Content type
		String contentType = Files.probeContentType(Paths.get(file.toURI()));
		textMap.put("type", contentType);
			
		//官方微信抓包时间格式是：Fri Jan 01 2010 00:00:00 GMT+0800
		SimpleDateFormat dateFormat = new SimpleDateFormat("E MMM dd yyyy HH:mm:ss Z", Locale.US);
		textMap.put("lastModifiedDate", dateFormat.format(System.currentTimeMillis())); //Thu Feb 23 2017 18:20:01 +0800
		
		textMap.put("size", Long.toString(file.length()));
		
		if(chunks > 1){
			textMap.put("chunks", Integer.toString(chunks));
			textMap.put("chunk", Integer.toString(chunk));
		}
		
		textMap.put("mediatype", fileSymbol);
		
		textMap.put("uploadmediarequest", gson.toJson(uploadMediaRequest));
				
		String[] items = passport.getCookie().split(";");
		String startString = "webwx_data_ticket=";		
		for(String item : items){			
			if(item.startsWith(startString)){
				textMap.put("webwx_data_ticket", item.substring(startString.length()));
				break;
			}
		}
		
		textMap.put("pass_ticket", passport.getPassTicket());
			
		//设置file的name，路径
		Map<String, String> fileMap = new HashMap<String, String>();
		fileMap.put("fileName", fileName);
		//String contentType = "";//image/png
		
		String host = Matchers.match("https://(.*)/cgi-bin/", baseUrl);
		String url1 = "https://file." + host + "/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json";
		String url2 = "https://file2." + host + "/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json";


		List<String> urlList = new ArrayList<String>();
		urlList.add(url1);
		urlList.add(url2);

//		urlList.add("https://file.wx2.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json");
//		urlList.add("https://file.wx8.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json");
//		urlList.add("https://file.wx.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json");
//		urlList.add("https://file.web2.wechat.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json");
//		urlList.add("https://file.web.wechat.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json");
//
//		urlList.add("https://file2.wx2.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json");
//		urlList.add("https://file2.wx8.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json");
//		urlList.add("https://file2.wx.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json");
//		urlList.add("https://file2.web2.wechat.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json");
//		urlList.add("https://file2.web.wechat.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json");
		
		for(String url : urlList){
			
			logger.info("尝试通过[{}]上传文件: ", url);
			
			String res = null;
					
			res = HttpUploadFile.formUpload(url, textMap, fileMap, contentType);
			
			if (!StringUtils.isBlank(res)) {
				JsonObject jsonObject = new JsonParser().parse(res).getAsJsonObject();
				if (jsonObject != null) {
					JsonObject baseResponse = jsonObject.getAsJsonObject("BaseResponse");
					
					if(baseResponse.get("Ret").getAsLong() == 0){
						logger.info("文件上传成功,可以进行发送");
						return res;
					}else{
						logger.info("文件上传失败，Ret：[{}]", baseResponse.get("Ret"));
					}					
				}
			}
			else{
				logger.info("Resposne为null");
			}
		}
		
		logger.info("文件上传失败");
		return null;
		
	}
	
	private static String findStr(String regex, String str) {
		Matcher matcher = Pattern.compile(regex).matcher(str);
		if (matcher.find()) {
			return matcher.group(1);
		}
		return null;
	}
	
	private static String getR() {
		return Integer.toString(~((int) System.currentTimeMillis()));// 转换成int后取反，因为js位运算只支持32位
	}

	public static String getAvatarUrl(){
	    return avatarUrl;
	}
	
    public static String getQrCodeFileRealPath(){
        return qrCodeFileRealPath;
    }

    public static Contact getUser(){
        return user;
    }
    
    public static void init(){
        
        baseUrl = null;
        redirectUrl = null;
        webpushUrl = null;       
        uuid = null;
        passport = new Passport();       
        syncKey = null;
        syncKeyString = null;
        user = null;
        
        qrCodeFileRealPath = null;
        avatarUrl = null;
    }
}
